Quest System 101
Category:Quests So you are looking to build a quest that's a bit more complex than: Go to A, Kill 15 B, return to C. Well you have come to the right place. This tutorial will demonstrate how to create complex quests in as simple a process as possible. Before getting into this, make sure you feel comfortable working in Djinni. You'll need to know how to edit 2DA files, create and edit Conversation files, and create and edit Quest Definition files. Of particular note is the Journal.2DA and Globaldlgflags.2DA files which are necessary for any complex quest. First we go in-depth with the Journal.2DA file. This file determines the different categories in the player's journal, and the journal entries that go in them. Your biggest categories are "Character" (All journal entries that go into Character tab ingame), and "Place" (All journal entries that go into Place tab ingame). There are others, but for the most part you wouldn't be creating new one in categories such as "Bestiary" and "Alchemy" for a quest. Of particular note is a hidden category called...you guessed it, "Hidden". This category is for any journal entry that you do not want to show inside the game, and are used to cause quest updates or events to happen ingame. Now you may be asking yourself, "Why would I want a hidden journal entry inside a quest?" Simple. Sometimes a complex quest has many small areas that do not necessarily require the player to be notified in a separate journal entry that they pulled a level, or killed 1 of 15 monsters. Or you are trying to start a quest and do not have a separate entry for a Character or Place yet. Now before we continue on with journal entries, let's move to another important areas: Global Dialog Flags. These are flags that are setup either in a script or during a conversation that you want to be set for an entire game. With a standard "local" flag in a conversation, once the conversation ends, the flag is reset. Therefore this causes some unpleasant results in a quest. But with a global flag, the flags are permanent and thus will determine how a conversation works. With a general conversation with multiple starting trees, The Witcher will start at the top and work it's way down to the first tree in which all criteria in flags and conditional scripts are met. Therefore it's good practice to have a standard "default" tree as the very bottom of a conversation. Now, global flags will allow you to control which trees are used once the first conversation takes place. As an example: You speak to a peasant woman whom is used in your quest. Her default conversation (at the bottom) plays until it's her time in the quest. At that point, a global flag had been set true by another character which now as caused the peasant woman to use a different line. When her conversation is done, a separate global flag then causes the next conversation to use a completely different tree (as you don't need another introduction in a quest, just the main facts if needed). And then, perhaps other characters cause different flags to change which opens up different parts of her conversation. The only limit is creativity. Finally, we will look into quest definitions. More information can be found elsewhere on the Wikia about quests, but the main parts are: Name, Short and Regular Descriptions, Type of Phase (Conditional, "AND", "OR" are the main ones), and the Completing Condition. Conditional phases require one condition to move to next phase. "AND" phases require both conditions (or nested "AND" or "OR" phases) to be met before moving to next phase. And "OR" phases require either condition (or nested "AND" or "OR" phases) to be met before moving to next phase. The main three Completion Conditions are: Hero Has Journal Entry, Hero has Item(s), and Dialog Line Chosen. With these three pieces above, and some basic scripts (AddJournalEntry("Category", "Journal_ID") and SetDialogFlagValue("Flag_Name", TRUE/FALSE)), you can accomplish anything with quests. Here is an example: 1) Create a new module. Save it and name it as "questfun" or whatever else you wish to use. 2) Add a new area. Doesn't matter which as we are simply setting up an example. 3) Create a spawnset file that has three random NPCs. They will be standing in place, ready for Geralt to speak to them. 4) Create spawn points and action points to cause these three NPCs to spawn. Test to make sure they working. 5) Create three separate conversation files. In each file, setup 4 trees with whatever dialogue you like. The last tree will become the default for each NPC. Then assign the conversation files to each NPC by going into their Character Template and adding it to conversation file property. 6) Save the journal.2da and globaldlgflags.2da to your module. While local files are fine, this is good practice if you plan to send a mod to others. 7) Create two conversation files called "character" and "place". 8) Create a new quest definition and save it to module as "questfun" or whatever else you wish to use. Now the foundation is set. We can begin building our mock quest. 9) In globaldlgflags.2da, add in the following entries by adding new rows at bottom: test1_talked, test2_talked, test3_talked, quest_solved, quest_unsolved. 10) In journal.2da, add in three Character journal entries: test1, test2, test3. 11) In journal.2da, add in two Hidden journal entry: test_start, test_failed. 12) In journal.2da, add in one Place journal entry: test_area. 13) In the Character conversation file, add in three separate trees for the three journal entries. The syntax for each entry is Name||Name and content. Then link each to the journal entry's ID from journal.2da in it's properties. Example: Test1||Test1 is a pretty nice person. 14) In the Place conversation file, add in one tree for the entry. The syntax for each entry is Location||Location and content. Then link to the journal entry's ID from journal.2da in it's properties. Example: TestArea||TestArea is pretty dark. 15) Create a basic script that has the line: EnableSpawnPhase("name of spawnset file in step 3", TRUE); and save this script to module as "test_init". Then type the script name into the "On Client Enter" portion of Module Properties. 16) Enter the Test1's conversation file. In the top line, set the "Required Flag" to quest_unsolved, and it's value to 1. In the second line, set the "Required Flag" to quest_solved, and it's value to 1. In the third line, set the "Required Flag" to test1_talked, and it's value to 1. Leave the fourth line without any flags. 17) Enter the Test2's conversation file. In the top line, set the "Required Flag" to quest_unsolved, and it's value to 1. In the second line, set the "Required Flag" to quest_solved, and it's value to 1. Also set a second "Required Flag" to test2_talked, and it's value to 1. In the third line, set the "Required Flag" to test1_talked, and it's value to 1. Leave the fourth line without any flags. 18) Enter the Test3's conversation file. In the top line, set the "Required Flag" to quest_unsolved, and it's value to 1. In the second line, set the "Required Flag" to quest_solved, and it's value to 1. Also set a second "Required Flag" to test3_talked, and it's value to 1. In the third line, set the "Required Flag" to test2_talked, and it's value to 1. Leave the fourth line without any flags. Now if you were to run your module (assuming a Start Point is set), you should see all 3 NPCs standing around. When you talk to them, they should be reverting to the last "default" line of dialogue every single time. If not, a step was missed above so make sure all steps are properly complete. And we continue... 19) Enter the Test1's conversation file. At the end of the last line, set the "After Flag" to test1_talked, and it's value to 1. 20) Enter the Test2's conversation file. At the end of the last line, set the "After Flag" to test2_talked, and it's value to 1. 21) Enter the Test3's conversation file. At the end of the last line, set the "After Flag" to test3_talked, and it's value to 1. Now if all went well, you should still get the default dialogue for all three the first time. And if talking to Test2, and Test3, it should continue. However, once you speak to Test1, it will now use the third line for Test1. It will also use the third line for Test2, while using last line for Test3. After talking to Test2, it should now use the third line for Test3. Since we are fully able to manipulate how dialogue works, we can now implement the quest. 22) In your "test_init" script, add a new line that is: AddJournalEntry("Hidden", "test_start"); 23) In your quest definition, right click the Quest Beginning and pick "Hero Has Journal Entry" as the Starting Condition. In the box that shows up, double click the white area and a new window should open up. Go to Hidden, and then choose "test_start" 24) Insert a new "OR" phase. Inside each subquest, insert a new "AND" phase. You should have one big "OR" phase that now has two "AND" phases in it. 25) In the top part of the "OR" phase, in the top part of it's "AND" phase, insert a new "Conditional" phase. The completion condition should be "Hero Has Journal Entry" The journal entry should be under Character, named Test2. 26) In the top part of the "OR" phase, in the bottom part of it's "AND" phase, insert a new "Conditional" phase. The completion condition should be "Hero Has Journal Entry" The journal entry should be under Character, named Test3. 27) In the bottom part of the "OR" phase, in the top part of it's "AND" phase, insert a new "Conditional" phase. The completion condition should be "Hero Has Journal Entry" The journal entry should be under Character, named Test1. 28) In the bottom part of the "OR" phase, in the bottom part of it's "AND" phase, insert a new "Conditional" phase. The completion condition should be "Hero Has Journal Entry" The journal entry should be under Character, named Test2. 29) Click the bottom "AND" phase, and in properties on right, you should see "On Phase Finished" and Available Actions. Open up the drop down and choose "add journal entry" and then hit add button. Then type "test_failed". 30) Now insert a new conditional phase completely after the entire "OR" phase so it's right before "Quest Completed". Set the Completion condition to "Hero Does Not Have Journal Entry" and then select the test_failed under Hidden. 31) Setup a new script that has: AddJournalEntry("Character", "test1"); Load the Test1 conversation. At the end of third line in "Action Script", type the name of new script. 32) Setup another script that has: AddJournalEntry("Character", "test2"); Load the Test2 conversation. At the end of third line in "Action Script", type the name of new script. 33) Setup a new script that has: AddJournalEntry("Character", "test3"); Load the Test3 conversation. At the end of third line in "Action Script", type the name of new script. At this point, we have it setup to have default lines for each NPC. If you speak to first NPC, it allows you to speak to the first NPC again for a new line, and a journal entry. It also allows you to speak to the second NPC for a new line and a journal entry. If you were to speak to the first NPC again, and then second, you complete the second part of the "OR" phase and therefore complete that entire phase. However, because you got the Hidden "test_failed" entry, you now fail at the next phase and the quest ends. If you, however, speak to second NPC, and then third, then you complete the quest successfully. Now we will use these results to manipulate how the NPCs react. 34) Setup a new script that has: SetDialogFlagValue("quest_unsolved", 1); 35) Setup another script that has: SetDialogFlagValue("quest_solved", 1); 36) In the quest file, click the top "AND" phase and in "On Phase Completed Action", type in the script with "quest_solved". 37) In the quest file, click the bottom "AND" phase and in "On Phase Completed Action", type in the script with "quest_unsolved". Now based on how the quest worked, we now have a flag set that is either going to have every NPC only talk their top dialogue tree (perhaps something angry as you failed quest) or the second dialogue tree (since you completed the quest correctly). It would be highly recommended to setup descriptions of each phase in the quest so you can see the quest progress to end properly. An example module (QuestFun.ADV)with all the steps above is attached to be able to see everything implemented. While this is a very basic look into the quest system, it shows how much more you can do with Journal Entries (Hidden or otherwise) and Global Dialog Flags.